Skip to content

Fix P10 and security review findings#170

Merged
kwsantiago merged 5 commits intomainfrom
fix/p10-and-security-findings
Feb 23, 2026
Merged

Fix P10 and security review findings#170
kwsantiago merged 5 commits intomainfrom
fix/p10-and-security-findings

Conversation

@kwsantiago
Copy link
Contributor

@kwsantiago kwsantiago commented Feb 23, 2026

Security: remove debug log leaking group pubkey, add timeout to BunkerService runBlocking, narrow catch(Throwable) to catch(Exception).

P10 Rule 5: add require() preconditions to AndroidKeystoreStorage and BunkerService security-critical functions.

P10 Rule 7: replace !! with safe local variable captures, fix delegated property smart cast issues.

P10 Rule 4: decompose oversized functions in MainActivity, ExportShareScreen, BunkerScreen, AppPermissionsScreen, PermissionsManagementScreen, SigningHistoryScreen, ImportShareScreen, and Nip55ContentProvider.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed potential null reference issues in error handling during import and connection workflows
    • Added timeout protection for permission lookups to prevent application hangs
    • Improved input validation for share names (now limited to 64 characters)
  • Refactor

    • Enhanced error messaging for initialization failures with consistent messaging
    • Improved internal component organization across settings and management screens

Security: remove debug log leaking group pubkey, add timeout to
BunkerService runBlocking, narrow catch(Throwable) to catch(Exception).

P10 Rule 5: add require() preconditions to AndroidKeystoreStorage and
BunkerService security-critical functions.

P10 Rule 7: replace !! with safe local variable captures, fix delegated
property smart cast issues.

P10 Rule 4: decompose oversized functions in MainActivity, ExportShareScreen,
BunkerScreen, AppPermissionsScreen, PermissionsManagementScreen,
SigningHistoryScreen, ImportShareScreen, and Nip55ContentProvider.
@kwsantiago kwsantiago self-assigned this Feb 23, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

Warning

Rate limit exceeded

@kwsantiago has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 31 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 9a3bf91 and 851b044.

📒 Files selected for processing (6)
  • app/src/main/kotlin/io/privkey/keep/nip46/BunkerScreen.kt
  • app/src/main/kotlin/io/privkey/keep/nip46/BunkerService.kt
  • app/src/main/kotlin/io/privkey/keep/nip46/Nip46ApprovalActivity.kt
  • app/src/main/kotlin/io/privkey/keep/nip55/AppPermissionsScreen.kt
  • app/src/main/kotlin/io/privkey/keep/nip55/PermissionsManagementScreen.kt
  • app/src/main/kotlin/io/privkey/keep/nip55/SigningHistoryScreen.kt

Walkthrough

This pull request refactors the application's Kotlin UI and storage layers by extracting large composable functions into modular components, centralizing dialog logic into reusable composables, hardening input validation in storage operations, and reorganizing permission/policy checking in the content provider.

Changes

Cohort / File(s) Summary
UI Component Extraction: Export & Import
ExportShareScreen.kt, ImportShareScreen.kt
Introduces PassphraseStrengthIndicator, ExportInputForm, ImportShareInputFields, and ImportStatusAndActions composables to modularize form UI and move validation/export/import logic into centralized callbacks with biometric authentication and error handling.
UI Component Extraction: Permissions & Apps
AppPermissionsScreen.kt, PermissionsManagementScreen.kt, SigningHistoryScreen.kt
Refactors large screens by extracting AppPermissionsListContent, RevokeAllPermissionsDialog, PermissionsGroupedList, DeletePermissionDialog, AppFilterDropdown, and SigningHistoryLogsList composables to modularize UI rendering and centralize dialog/list logic.
Dialog & Settings Extraction: Main & Bunker
MainActivity.kt, BunkerScreen.kt
Replaces inline AlertDialog usage with composables (KillSwitchConfirmDialog, PinMismatchDialog, AddBunkerRelayDialog, RevokeAllClientsDialog) and extracts ShareSettingsSection, DatabaseManagementSection, SettingsFooterLinks for consolidated settings rendering.
Permission & Policy Enforcement
Nip55ContentProvider.kt, BunkerService.kt
Introduces velocity limit checks, auto-sign policy evaluation with PolicyResult sealed hierarchy, permission checks with risk assessment, deterministic notification ID computation, and 5-second timeout wrapper for permission lookups with client tracking cap.
Null Safety & Error Handling
ImportNsecScreen.kt, ConnectedAppsScreen.kt, Nip46ApprovalActivity.kt
Adds local variable captures for null-safe UI rendering (currentScanError, currentLoadError) and changes exception handling from Throwable to Exception in permission saving.
Input Validation & Storage Guards
AndroidKeystoreStorage.kt
Adds metadata validation (identifier, threshold, totalShares within UShort range) and input guards requiring non-blank keys and non-empty data across storeShareByKey, loadShareByKey, deleteShareByKey, and setActiveShareKey methods.
App Initialization & Debug Logging
KeepMobileApp.kt
Changes initialization error message to generic "Failed to initialize application" and replaces raw groupPubkey logging with a boolean hasGroup flag for debug output.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 Composables scattered, now gathered with care,
Dialogs extracted, logic laid bare,
Validation guards strong, permissions run tight,
Modular screens dance in the digital light,
Refactored with grace, the code shines so bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly corresponds to the PR's stated objectives, encompassing security fixes and P10 code quality improvements across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/p10-and-security-findings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kwsantiago
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/kotlin/io/privkey/keep/nip55/SigningHistoryScreen.kt (1)

43-43: ⚠️ Potential issue | 🟡 Minor

loadError is set but never displayed in the UI.

The loadError state is updated on failure (lines 59, 61, 75) but is never rendered anywhere in the composable. Users won't see feedback when log loading fails. Consider displaying it (e.g., via a StatusCard or Text) or removing the variable if error display isn't needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/io/privkey/keep/nip55/SigningHistoryScreen.kt` at line
43, The loadError state in SigningHistoryScreen is updated on failures but never
rendered; either display it or remove it: locate the mutable state var loadError
and, inside SigningHistoryScreen's Compose tree (e.g., under the header or where
empty/error states are shown), add a conditional UI that shows loadError when
non-null (use your existing StatusCard or a Text with appropriate style) so
users see the error, or if you intentionally don't want to surface load errors
remove the loadError variable and its assignments (in functions that set it) to
avoid dead state.
🧹 Nitpick comments (6)
app/src/main/kotlin/io/privkey/keep/nip46/Nip46ApprovalActivity.kt (1)

183-185: Re-throw CancellationException to preserve structured concurrency.

catch (e: Exception) will intercept kotlinx.coroutines.CancellationException (which extends IllegalStateException → RuntimeException → Exception). If the lifecycleScope is cancelled while store.grantPermission(...) is executing inside withContext(Dispatchers.IO), the cancellation signal is swallowed, the error path logs it as a routine failure, and execution falls through to finish() — breaking Kotlin's structured-concurrency contract.

The original catch (t: Throwable) had the same flaw; this change is the right moment to fix it.

♻️ Proposed fix
-            } catch (e: Exception) {
-                if (BuildConfig.DEBUG) Log.e(TAG, "Failed to persist permission: ${e::class.simpleName}")
+            } catch (e: Exception) {
+                if (e is kotlinx.coroutines.CancellationException) throw e
+                if (BuildConfig.DEBUG) Log.e(TAG, "Failed to persist permission: ${e::class.simpleName}")
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/io/privkey/keep/nip46/Nip46ApprovalActivity.kt` around
lines 183 - 185, The catch block in Nip46ApprovalActivity (around the
store.grantPermission call executed from
lifecycleScope/withContext(Dispatchers.IO)) is catching Exception and thus
swallowing kotlinx.coroutines.CancellationException; adjust the handler so
CancellationException is re-thrown (e.g., detect
kotlin.coroutines.cancellation.CancellationException or Throwable of that type
and throw it) before logging/handling other exceptions so structured concurrency
is preserved and cancellations propagate correctly.
app/src/main/kotlin/io/privkey/keep/nip46/BunkerScreen.kt (1)

262-297: Consider disabling the "Add" button or showing a loading indicator during async validation.

When the user taps "Add", the isInternalHost check (single relay) or addBunkerRelays (bunker URL) runs asynchronously. During this time the button remains tappable with no visual feedback. A brief loading state would improve UX and prevent duplicate submissions, though the outcome is idempotent so this isn't a correctness issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/io/privkey/keep/nip46/BunkerScreen.kt` around lines 262 -
297, Add a transient loading state to prevent duplicate taps and show feedback:
introduce a mutable Boolean (e.g. isAdding/isLoading) that is checked at the
start of the TextButton onClick and used to disable the Add button and show a
small loading indicator in the button. Set isLoading = true immediately before
launching async work (both the scope.launch branch that calls addBunkerRelays
and the withContext(Dispatchers.IO) BunkerConfigStore.isInternalHost branch),
and ensure you reset isLoading = false in all completion paths
(onSuccess/onFailure, internal host branch, and after
onRelaysUpdated/dismissDialog) so the button re-enables and the indicator hides;
keep existing variables like newRelayUrl, addBunkerRelays, validateRelayUrl,
onRelaysUpdated and dismissDialog intact.
app/src/main/kotlin/io/privkey/keep/nip55/AppPermissionsScreen.kt (1)

193-208: AppPermissionsListContent has a large parameter surface (14 params) with embedded business logic.

This composable receives stores and coroutineScope directly, coupling UI to the data layer. Consider extracting the business operations into callbacks passed from the parent in a future refactor, similar to how ImportShareInputFields only receives display state and event callbacks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/io/privkey/keep/nip55/AppPermissionsScreen.kt` around
lines 193 - 208, AppPermissionsListContent currently takes many data-layer types
(SignPolicyStore, PermissionStore, CoroutineScope) and implements business
logic; refactor by removing stores and coroutineScope from its signature and
replace them with explicit UI event callbacks and minimal display state (e.g.,
onLoadPermissions(), onRevokeAllRequested(), onUpdateExpiry(expiry: Long),
onSaveSettings(settings: Nip55AppSettings?), onChangeAppState(state: AppState),
plus any simple data lists or flags needed for rendering), then move the actual
business operations (calls into SignPolicyStore/PermissionStore and
coroutineScope launches) up to the parent caller that owns those stores; update
callers to pass the new callbacks and keep AppPermissionsListContent as a pure
UI/composable that only receives display state and event handlers.
app/src/main/kotlin/io/privkey/keep/nip46/BunkerService.kt (2)

65-65: Evicting rate-limit history should also clean per‑client state

When the cap is hit, only clientRequestHistory is evicted. clientBackoffUntil and clientConsecutiveRequests can still grow without bound and retain stale backoff for evicted clients. Consider clearing related per‑client state when evicting.

♻️ Suggested tweak to evict related state
 if (clientRequestHistory.size >= MAX_TRACKED_CLIENTS && !clientRequestHistory.containsKey(clientPubkey)) {
-    clientRequestHistory.keys.firstOrNull()?.let { clientRequestHistory.remove(it) }
+    clientRequestHistory.keys.firstOrNull()?.let { evicted ->
+        clientRequestHistory.remove(evicted)
+        clientBackoffUntil.remove(evicted)
+        clientConsecutiveRequests.remove(evicted)
+    }
 }

Also applies to: 171-173

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/io/privkey/keep/nip46/BunkerService.kt` at line 65, The
eviction currently only removes entries from clientRequestHistory
(MAX_TRACKED_CLIENTS) but leaves related per-client state in clientBackoffUntil
and clientConsecutiveRequests, letting those maps grow and preserving stale
backoff; update the eviction logic (the block handling MAX_TRACKED_CLIENTS) to
also delete the same client keys from clientBackoffUntil and
clientConsecutiveRequests whenever you evict from clientRequestHistory (ensure
you use the same clientId/key used by clientRequestHistory removal), and apply
the same cleanup in the other eviction site that mirrors this logic so all three
maps stay in sync.

41-41: Consider surfacing timeout vs. "no decision"

withTimeoutOrNull returns null on timeout, which is indistinguishable from "no stored decision." If you want visibility into timeouts, consider withTimeout + TimeoutCancellationException logging.

🔍 Optional timeout visibility
 return runCatching {
     runBlocking(Dispatchers.IO) {
-        withTimeoutOrNull(5_000L) {
-            store.getPermissionDecision(callerPackage, requestType, eventKind)
-        }
+        try {
+            withTimeout(5_000L) {
+                store.getPermissionDecision(callerPackage, requestType, eventKind)
+            }
+        } catch (e: TimeoutCancellationException) {
+            if (BuildConfig.DEBUG) Log.w(TAG, "Permission lookup timed out")
+            null
+        }
     }
 }.getOrNull()

Also applies to: Nip55ContentProvider.kt

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/io/privkey/keep/nip46/BunkerService.kt` at line 41, The
current use of withTimeoutOrNull in BunkerService.kt (and similarly in
Nip55ContentProvider.kt) hides whether a null result came from a timeout or from
"no stored decision"; replace with withTimeout around the same call and add a
catch for kotlinx.coroutines.TimeoutCancellationException in the surrounding
coroutine scope to explicitly log/handle timeouts (include function names where
used, e.g., the method that calls withTimeoutOrNull in BunkerService and the
corresponding provider method in Nip55ContentProvider), so the catch can emit a
clear processLogger/error or metrics entry indicating a timeout vs a genuine
null decision; keep the original null-handling logic separate from the timeout
handling.
app/src/main/kotlin/io/privkey/keep/nip55/PermissionsManagementScreen.kt (1)

225-266: Optional: memoize grouped permissions to reduce recomposition work.

groupBy runs on every recomposition. If the list is large, consider memoizing by permissions to avoid repeated grouping.

♻️ Suggested tweak
-    val groupedPermissions = permissions.groupBy { it.callerPackage }
+    val groupedPermissions = remember(permissions) {
+        permissions.groupBy { it.callerPackage }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/io/privkey/keep/nip55/PermissionsManagementScreen.kt`
around lines 225 - 266, The grouping of permissions in PermissionsGroupedList is
recomputed on every recomposition; replace the direct call to
permissions.groupBy { it.callerPackage } with a memoized value (e.g., use
remember or derivedStateOf keyed on permissions) so groupedPermissions is only
recalculated when the permissions list changes, leaving the rest of the function
(LazyColumn, AppPermissionHeader, PermissionCard) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/kotlin/io/privkey/keep/nip55/AppPermissionsScreen.kt`:
- Around line 167-178: The current coroutine in coroutineScope.launch swallows
errors from permissionStore.revokePermission(packageName) and
revokeNip46Client(context, pubkey) because both calls use runCatching with no
handling, so failures still cause onDismissDialog() and onDismissScreen() to
run; update the block in AppPermissionsScreen.kt to inspect the runCatching
results (or use try/catch) for permissionStore.revokePermission and
revokeNip46Client, log the exception (use Timber or Log) in debug builds and
surface a user-visible failure (e.g., show a Toast or Snackbar) if either
revocation fails, and only call onDismissDialog() / onDismissScreen() when
revocation succeeds (or branch so NIP-46 errors don’t hide permission revocation
failures); reference permissionStore.revokePermission(packageName),
revokeNip46Client(context, pubkey), coroutineScope.launch,
withContext(Dispatchers.IO), onDismissDialog, and onDismissScreen when making
these changes.

In `@app/src/main/kotlin/io/privkey/keep/nip55/PermissionsManagementScreen.kt`:
- Around line 103-126: The onDecisionChange handler currently silently returns
when findRequestType(permission.requestType) is null; update it to surface an
error before returning: inside the PermissionsGroupedList onDecisionChange
lambda (where findRequestType is called), when requestType is null set a visible
error state (e.g., set loadError or trigger the existing snackbar mechanism)
with a clear message like "Unknown request type" and then return, so users get
feedback; keep the subsequent coroutineScope launch path unchanged and reference
findRequestType, onDecisionChange, permissionStore.updatePermissionDecision, and
loadError/snackbar state so the fix is applied in the correct handler.

---

Outside diff comments:
In `@app/src/main/kotlin/io/privkey/keep/nip55/SigningHistoryScreen.kt`:
- Line 43: The loadError state in SigningHistoryScreen is updated on failures
but never rendered; either display it or remove it: locate the mutable state var
loadError and, inside SigningHistoryScreen's Compose tree (e.g., under the
header or where empty/error states are shown), add a conditional UI that shows
loadError when non-null (use your existing StatusCard or a Text with appropriate
style) so users see the error, or if you intentionally don't want to surface
load errors remove the loadError variable and its assignments (in functions that
set it) to avoid dead state.

---

Nitpick comments:
In `@app/src/main/kotlin/io/privkey/keep/nip46/BunkerScreen.kt`:
- Around line 262-297: Add a transient loading state to prevent duplicate taps
and show feedback: introduce a mutable Boolean (e.g. isAdding/isLoading) that is
checked at the start of the TextButton onClick and used to disable the Add
button and show a small loading indicator in the button. Set isLoading = true
immediately before launching async work (both the scope.launch branch that calls
addBunkerRelays and the withContext(Dispatchers.IO)
BunkerConfigStore.isInternalHost branch), and ensure you reset isLoading = false
in all completion paths (onSuccess/onFailure, internal host branch, and after
onRelaysUpdated/dismissDialog) so the button re-enables and the indicator hides;
keep existing variables like newRelayUrl, addBunkerRelays, validateRelayUrl,
onRelaysUpdated and dismissDialog intact.

In `@app/src/main/kotlin/io/privkey/keep/nip46/BunkerService.kt`:
- Line 65: The eviction currently only removes entries from clientRequestHistory
(MAX_TRACKED_CLIENTS) but leaves related per-client state in clientBackoffUntil
and clientConsecutiveRequests, letting those maps grow and preserving stale
backoff; update the eviction logic (the block handling MAX_TRACKED_CLIENTS) to
also delete the same client keys from clientBackoffUntil and
clientConsecutiveRequests whenever you evict from clientRequestHistory (ensure
you use the same clientId/key used by clientRequestHistory removal), and apply
the same cleanup in the other eviction site that mirrors this logic so all three
maps stay in sync.
- Line 41: The current use of withTimeoutOrNull in BunkerService.kt (and
similarly in Nip55ContentProvider.kt) hides whether a null result came from a
timeout or from "no stored decision"; replace with withTimeout around the same
call and add a catch for kotlinx.coroutines.TimeoutCancellationException in the
surrounding coroutine scope to explicitly log/handle timeouts (include function
names where used, e.g., the method that calls withTimeoutOrNull in BunkerService
and the corresponding provider method in Nip55ContentProvider), so the catch can
emit a clear processLogger/error or metrics entry indicating a timeout vs a
genuine null decision; keep the original null-handling logic separate from the
timeout handling.

In `@app/src/main/kotlin/io/privkey/keep/nip46/Nip46ApprovalActivity.kt`:
- Around line 183-185: The catch block in Nip46ApprovalActivity (around the
store.grantPermission call executed from
lifecycleScope/withContext(Dispatchers.IO)) is catching Exception and thus
swallowing kotlinx.coroutines.CancellationException; adjust the handler so
CancellationException is re-thrown (e.g., detect
kotlin.coroutines.cancellation.CancellationException or Throwable of that type
and throw it) before logging/handling other exceptions so structured concurrency
is preserved and cancellations propagate correctly.

In `@app/src/main/kotlin/io/privkey/keep/nip55/AppPermissionsScreen.kt`:
- Around line 193-208: AppPermissionsListContent currently takes many data-layer
types (SignPolicyStore, PermissionStore, CoroutineScope) and implements business
logic; refactor by removing stores and coroutineScope from its signature and
replace them with explicit UI event callbacks and minimal display state (e.g.,
onLoadPermissions(), onRevokeAllRequested(), onUpdateExpiry(expiry: Long),
onSaveSettings(settings: Nip55AppSettings?), onChangeAppState(state: AppState),
plus any simple data lists or flags needed for rendering), then move the actual
business operations (calls into SignPolicyStore/PermissionStore and
coroutineScope launches) up to the parent caller that owns those stores; update
callers to pass the new callbacks and keep AppPermissionsListContent as a pure
UI/composable that only receives display state and event handlers.

In `@app/src/main/kotlin/io/privkey/keep/nip55/PermissionsManagementScreen.kt`:
- Around line 225-266: The grouping of permissions in PermissionsGroupedList is
recomputed on every recomposition; replace the direct call to
permissions.groupBy { it.callerPackage } with a memoized value (e.g., use
remember or derivedStateOf keyed on permissions) so groupedPermissions is only
recalculated when the permissions list changes, leaving the rest of the function
(LazyColumn, AppPermissionHeader, PermissionCard) unchanged.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a05f63e and 9a3bf91.

📒 Files selected for processing (14)
  • app/src/main/kotlin/io/privkey/keep/ExportShareScreen.kt
  • app/src/main/kotlin/io/privkey/keep/ImportNsecScreen.kt
  • app/src/main/kotlin/io/privkey/keep/ImportShareScreen.kt
  • app/src/main/kotlin/io/privkey/keep/KeepMobileApp.kt
  • app/src/main/kotlin/io/privkey/keep/MainActivity.kt
  • app/src/main/kotlin/io/privkey/keep/nip46/BunkerScreen.kt
  • app/src/main/kotlin/io/privkey/keep/nip46/BunkerService.kt
  • app/src/main/kotlin/io/privkey/keep/nip46/Nip46ApprovalActivity.kt
  • app/src/main/kotlin/io/privkey/keep/nip55/AppPermissionsScreen.kt
  • app/src/main/kotlin/io/privkey/keep/nip55/ConnectedAppsScreen.kt
  • app/src/main/kotlin/io/privkey/keep/nip55/Nip55ContentProvider.kt
  • app/src/main/kotlin/io/privkey/keep/nip55/PermissionsManagementScreen.kt
  • app/src/main/kotlin/io/privkey/keep/nip55/SigningHistoryScreen.kt
  • app/src/main/kotlin/io/privkey/keep/storage/AndroidKeystoreStorage.kt

@kwsantiago kwsantiago merged commit f26120f into main Feb 23, 2026
3 checks passed
@kwsantiago kwsantiago deleted the fix/p10-and-security-findings branch February 23, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant